/*
 * Converters.java
 *
 * Created on 4 juin 2006, 23:16
 *
 * Utility routines to convert values, either from NMEA, either from numeric to strings.
 * Try to reduce memory allocation as much as possible.
 * Try to be fast.
 */

package courselog.gpsutils;

/**
 * Utility routines to convert values, either from NMEA, either from numeric to strings.
 * Try to reduce memory allocation as much as possible.
 * Try to be fast.
 * @author C. Rigal
 */
public class Converters {
    
    /**
     * Number of kilometers per nautic miles
     */
    public static final double KILOMETERSPERNAUTICMILES=1.852d;
    
    /**
     * Convert angle to a latitude string (N/S).
     * Switch value for <I>ddmmss()</I> method.
     * @see Converters#ddmmss(double,int)
     */
    public static final int DDMMSS_LAT=0;
    
    /**
     * Convert angle to a longitude string (E/W).
     * Switch value for <I>ddmmss()</I> method.
     * @see Converters#ddmmss(double,int)
     */
    public static final int DDMMSS_LON=1;
    
    /** Option specifying units in (km/h, m). */
    public static final int FORMAT_KM_M=0;
    
    /** Option specifying units in (Kt, Nm). */
    public static final int FORMAT_KT_NM=1;
    
    /** Flag indicating the format for converting speeds and distances.
     * One of FORMAT_KM_M or FORMAT_KT_NM.
     **/
    public static int classCurrentFormat=FORMAT_KM_M;
    
    /**
     * Convert hexadecimal value of length <I>len</I>, starting
     * at <I>idxChk</I> in buffer <I>charChk</I>.
     * @param charChk Buffer containing the hexadecimal value.
     * @param idxChk Index of first character in the buffer.
     * @param len Length of the value to convert.
     * @return Hexadecimal value.
     */
    public static int valByteHex(byte[] charChk, int idxChk, int len) {
        int vCharHex=0;
        int valTot=0;
        
        for(int idx=len-1; idx>=0; idx--) {
            valTot <<=4;
            if( charChk[idxChk]>'9' ) {
                vCharHex = (charChk[idxChk]-'A'+0x0A);
            } else {
                vCharHex = (charChk[idxChk]-'0');
            }
            valTot += vCharHex;
            
            idxChk++;
        }
        
        return valTot;
    }
    
    /**
     * Convert an int into an hexadecimal serie of char into a buffer.
     * @param val Value to convert.
     * @param charChk Buffer to put the hex characters in.
     * @param idxChk Starting index where to put the Hex characters.
     * @param len Length of the hex string to produce.
     */
    public static void intToHexBytes(int val, byte[] charChk, int idxChk, int len) {
        int vCharHex;
        int mask=0x0F;
        int iShift=0;
        for(int idx=len-2; idx>=0; idx--) {
            mask <<=4;
            iShift +=4;
        }
        
        for(int idx=len-1; idx>=0; idx--) {
            vCharHex=(val&mask)>>iShift;
            if( vCharHex>9 ) {
                charChk[idxChk]=(byte) ((vCharHex-10)+'A');
            } else {
                charChk[idxChk]=(byte) (vCharHex+'0');
            }
            iShift -=4;
            mask >>=4;
            idxChk++;
        }
        
        return;
    }
    
    /**
     * Change a double value of latitude or longitude parsed from NMEA
     * sentence into a decimal degree value.
     * NMEA values are formatted as DDMM.MMMM.
     * @param nMEAVal Degrees value from NMEA format.
     * @return Degree values with decimal degrees.
     */
    public static double nMEA2Deg(double nMEAVal) {
        double vA;
        double minutes;
        
        vA=Math.abs(nMEAVal);
        minutes= vA-(Math.floor(vA/100.0d)*100.0d);
        vA=Math.floor(vA/100.0d)+minutes/60.0d;
        
        return (nMEAVal>=0?vA:-vA);
    }
    
    /**
     * Convert an angle into a new String.
     * The string is formatted into <CODE>DD°MM'SS"X</CODE> where X depends
     * on the value of flag <I>flgLatLon</I> (latitude, longitude).
     * @param ang Angle values in decimal degrees.
     * @param flgLatLon Flag indicating wether its a latitude or a longitude.
     * @return Formatted string.
     * @see Converters#DDMMSS_LAT
     */
    public static String ddmmss(double ang, int flgLatLon) {
        StringBuffer stB=new StringBuffer(15);
        char idL=' ';
        char idType;
        int vDeg;
        byte dig;
        
        // compute degrees depending on lat/lon
        if(flgLatLon==DDMMSS_LAT) {
            idL=(ang>=0?'N':'S');
            idType='°';
            if(ang<0) ang = -ang;
            vDeg= (int) ang;
        }else{
            idL=(ang>=0?'E':'W');
            idType='°';
            if(ang<0) ang = -ang;
            vDeg=(int) ang;
            dig=(byte)(vDeg/100);
            stB.append((char) (dig+'0'));
            vDeg -= dig*100;
        }
        dig=(byte)(vDeg/10);
        stB.append((char) (dig+'0'));
        vDeg -= dig*10;
        stB.append((char) (vDeg+'0'));
        stB.append(idType);
        
        ang -= (int) ang;
        ang *=60;
        vDeg=(int) ang; // int value is minutes
        dig=(byte)(vDeg/10);
        stB.append((char) (dig+'0'));
        vDeg -= dig*10;
        stB.append((char) (vDeg+'0'));
        stB.append('\'');
        
        ang -= (int) ang;
        ang *=60;
        vDeg=(int) ang; // int value is seconds
        dig=(byte)(vDeg/10);
        stB.append((char) (dig+'0'));
        vDeg -= dig*10;
        stB.append((char) (vDeg+'0'));
        stB.append('.');
        
        ang -= (int) ang;
        ang *=100;
        vDeg=(int) ang; // int value is seconds
        dig=(byte)(vDeg/10);
        stB.append((char) (dig+'0'));
        vDeg -= dig*10;
        stB.append((char) (vDeg+'0'));
        stB.append('"');
        
        if(idL!=' ') {
            stB.append(idL);
        }
        
        return stB.toString();
    }
    
    /**
     * Generate a new String from a double values expressed with
     * <CODE>hhmmss.ssss</CODE> value into a string HH:MM:SS
     * @param utc Time value obtained from NMEA.
     * @return new String HH:MM:SS (decimal of seconds are not used,
     * not accurate.
     */
    public static String formatUTC(double utc) {
        StringBuffer stB= new StringBuffer(8);
        int val;
        byte dig;
        val = (int) utc;
        // Hours
        dig= (byte) (val/100000);
        stB.append((char) (dig+'0'));
        val-= dig*100000;
        dig= (byte) (val/10000);
        stB.append((char) (dig+'0'));
        stB.append(':');
        val-= dig*10000;
        dig= (byte) (val/1000);
        stB.append((char) (dig+'0'));
        val-= dig*1000;
        dig= (byte) (val/100);
        stB.append((char) (dig+'0'));
        stB.append(':');
        val-= dig*100;
        dig= (byte) (val/10);
        stB.append((char) (dig+'0'));
        val-= dig*10;
        dig= (byte) val;
        stB.append((char) (dig+'0'));
        
        return stB.toString();
    }
    
    /**
     * Provides a new String indicating a duration in seconds.
     * Format is HHHhMM'SSS"
     * @param dur Duration value in seconds.
     * @return new String formatted.
     */
    public static String formatDuration(double dur) {
        StringBuffer stB=new StringBuffer(15);
        char idType='h';
        int vHour;
        byte dig;
        
        // Convert to hours
        dur= dur/3600d;
        if(dur>999.99999d) dur=999.99999d;
        vHour=(int) dur;
        dig=(byte)(vHour/100);
        stB.append((char) (dig+'0'));
        vHour -= dig*100;
        
        dig=(byte)(vHour/10);
        stB.append((char) (dig+'0'));
        vHour-= dig*10;
        stB.append((char) (vHour+'0'));
        stB.append(idType);
        
        dur -= (int) dur;
        dur *=60;
        vHour=(int) dur; // int value is minutes
        dig=(byte)(vHour/10);
        stB.append((char) (dig+'0'));
        vHour -= dig*10;
        stB.append((char) (vHour+'0'));
        stB.append('\'');
        
        dur -= (int) dur;
        dur *=60;
        vHour=(int) dur; // int value is seconds
        dig=(byte)(vHour/10);
        stB.append((char) (dig+'0'));
        vHour -= dig*10;
        stB.append((char) (vHour+'0'));
        stB.append('"');
        
        return stB.toString();
    }
    
    /**
     * Convert an altitude in meters into a new String.
     * Format AAAA.A m
     * @param alt Altitude value in degrees.
     * @return new String with the altitude.
     */
    public static String formatAltitude(double alt) {
        byte[] buff=new byte[10];
        
        int lg=writeDblInBuffer(buff, 0, 6, 1, alt);
        buff[lg++]=' ';
        buff[lg++]='m';
        return new String(buff,0, lg);
    }
    
    /**
     * Formats a heading into a new String.
     * Format is HHH.H°
     * @param hdg Heading value in degrees.
     * @return New String formatted.
     */
    public static String formatHeading(double hdg) {
        return String.valueOf((int)hdg)+"°";
    }
    
    /**
     * Format a distance value in meters into a string.
     * Format is DDD.DDD km.
     * @param len Distance to format in meters.
     * @return New formatted string.
     */
    public static String formatDistance(double len) {
        if(classCurrentFormat==FORMAT_KT_NM) {
            len /= KILOMETERSPERNAUTICMILES;
        }
        
        byte[] buff= new byte[20];
        int lg=Converters.writeDblInBuffer(buff, 0, 9, 3, len/1000.0d);
        buff[lg++]=' ';
        
        if(classCurrentFormat==FORMAT_KT_NM) {
            buff[lg++]='N';
            buff[lg++]='m';
        }else{
            buff[lg++]='k';
            buff[lg++]='m';
        }
        return new String(buff, 0, lg);
    }
    
    /**
     * Format a speed in km/h.
     * New String format is VVV.V km/h .
     * @param spd Speed value in km/h.
     * @return New formatted String.
     */
    public static String formatSpeed(double spd) {
        if(classCurrentFormat==FORMAT_KT_NM) {
            spd /= KILOMETERSPERNAUTICMILES;
            return String.valueOf(((int)(spd*10.0d))/10.0d)+" Kt";
        }
        return String.valueOf(((int)(spd*10.0d))/10.0d)+" km/h";
    }
    
    /**
     * Write an int value <I>val</I> into buffer <I>b</I>.
     * Characters start at <I>start</I> with the sign character or a blank.
     * Length of the int is <I>len</I> with initial '0' if needed.
     * @param b Buffer to put value in.
     * @param start Start index in buffer (sign or blank).
     * @param len Length (number of digits excluding sign).
     * @param val Value to put in the buffer.
     * @return index right after the last formatted character.
     */
    public static int writeIntInBuffer(byte[] b, int start, int len, int val) {
        int base=1;
        int idx;
        for(idx=len-1; idx>0; idx--) base *=10;
        if( val<0 ) {
            b[start]='-';
            val= -val;
        } else b[start]=' ';
        
        start++;
        if(val>(10*base)) {
            val=10*base-1;
        }
        for(idx=start; (idx<b.length)&&(base>0); idx++) {
            byte digit= (byte) (val/base);
            val -= digit*base;
            if(digit>9) {
                digit=9;
            }
            b[idx]= (byte) (digit+'0');
            base /=10;
        }
        return idx;
    }
    
    /**
     * Reads an int value from buffer <I>b</I>.
     * Characters start at <I>start</I> with the sign character or leading blanks.
     * The int should stop at <i>stop</i> index.
     * Returns 0 if no valid value was read (<B>be careful, also returns 0 if this was 0!</B>)
     * @param b Buffer to put value in.
     * @param start Start index in buffer (sign or blank).
     * @param stop Index of the position where conversion stops.
     * @return integer value that was read.
     */
    public static int readIntFromBuffer(byte[] b, int start, int stop) {
        int base=1;
        int val=0;
        boolean flagWaitForSign=true;
        
        while(flagWaitForSign) {
            if(b[start]==' ') {
                start++;
                if(start>stop) return val;
            }else if(b[start]=='+') {
                flagWaitForSign=false;
            }else if(b[start]=='+') {
                base=-1;
                flagWaitForSign=false;
            }else{
                return val;
            }
        }
        return val;
    }
    
    /**
     * Write a double value <I>val</I> into buffer <I>b</I>.
     * Characters start at <I>start</I> with the sign character or a blank.
     * Length of the integer part is <I>len-dec-1</I> with initial '0' if needed.
     * A decimal point is put then the deciaml values with <I>dec</I> digits.
     * @param b Buffer to put value in.
     * @param start Start index in buffer (sign or blank).
     * @param len Total length excluding the leading sign or blank.
     * @param dec Number of decimals.
     * @param val Double value to format.
     * @return index right after the last formatted character.
     */
    public static int writeDblInBuffer(byte[] b, int start, int len, int dec, double val) {
        double base=1;
        int idx;
        // first round val
        // Compute value of base so that we can start
        for(idx=dec-1; idx>=0; idx--) base *=10;
        double rndVal=val*base;
        if(val<0) rndVal= -rndVal;
        int iVal = (int) (rndVal+0.5d);
        int iEnt = (int) (iVal/base);
        int iDec = iVal-((int) (iEnt*base));
        if(val<0) iEnt= -iEnt;
        
        idx=writeIntInBuffer(b, start, len-dec-1, iEnt); // write integer part to buffer
        int idxPt=idx;
        idx=writeIntInBuffer(b, idx, dec, iDec);
        
        if( val<0 ) b[start]='-';
        else b[start]=' ';
        b[idxPt]='.';
        
        return idx;
    }
    
    /**
     * General parser of a CSV sentence.
     * Uses a serie of characters indicating how to interpret CSV data and
     * puts values into associated vector.
     * <PRE>'V' means a double.
     * 'C' means a single char.
     * 'I' means an integer.</PRE>
     *
     * <PRE>Example : code of NMEA GGA sentence is "VVCVCIIVVCVCVI".
     * 2 first values are double (UTC and lat), then comes 'N' or 'S' indicator,
     * then longitude with 'W' or 'E' then status as int, then number of satellites.
     * tVal[0]=UTC, tVal[1]=Lat, tVal[2]=lon, ...
     * tInt[0]=status, tInt[1]=nb sats,
     * tChar[0]='N' or 'S', tChar[1]='W' or 'E', ...</PRE>
     * @param code Sequence if identifier to decode the sentence
     * @param b Buffer containing data
     * @param idxS Start of relevant data.
     * @param iEnd End of relevant data.
     */
    public static void parseCSVSentence(String code, byte[] b, int idxS, int iEnd,
            double tVal[], int tInt[], char tChar[]) {
        int idxC = idxS;
        int idxParse=0;
        int idxI=0;
        int idxV=0;
        int idxH=0;
        iEnd++; // use iEnd as a bound
        while( idxC<=iEnd ) {
            switch( code.charAt(idxParse)) {
                case 'V' : // parse a float value
                    double base=0.1d;
                    if(b[idxC]=='-') { // Negative value
                        base=-base;
                        idxC++;
                    }
                    if((b[idxC]==' ')||(b[idxC]=='+')) {
                        // Positive value
                        idxC++;
                    }
                    // Compute base value, at end of loop base is the power of most
                    // significant digit
                    for(int idxPow=idxC; (idxPow<iEnd)&&(b[idxPow]!=',')&&(b[idxPow]!='.');
                    idxPow++) {
                        base *=10.0d;
                    }
                    tVal[idxV]=0.0d;
                    for(; (idxC<iEnd)&&(b[idxC]!=','); idxC++ ) {
                        if(b[idxC]!='.') {
                            tVal[idxV]+= base*(b[idxC]-'0');
                            base /=10.0d;
                        }
                    }
/*                    double dec=-1.0f;
                    tVal[idxV]=0;
                    for(; (idxC<iEnd)&&(b[idxC]!=','); idxC++) {
                        if( b[idxC]=='.' ) {
                            dec=0.1d;
                        } else {
                            if(dec>0.0) {
                                tVal[idxV] += (b[idxC]-'0')*dec;
                                dec /= 10.0d;
                            } else {
                                tVal[idxV] = tVal[idxV]*10.0f+(b[idxC]-'0');
                            }
                        }
                    } */
                    idxV++;
                    break;
                case 'I': // Parse an int value
                    tInt[idxI]=0;
                    int signInt=1;
                    if(b[idxC]=='-') { // Negative value
                        signInt=-1;
                        idxC++;
                    }
                    if((b[idxC]==' ')||(b[idxC]=='+')) {
                        // Positive value
                        idxC++;
                    }
                    for(; (idxC<iEnd)&&(b[idxC]!=','); idxC++) {
                        tInt[idxI] = tInt[idxI]*10+signInt*(b[idxC]-'0');
                    }
                    idxI++;
                    break;
                case 'C': // Parse a single character value
                    if(idxC==iEnd) { // No character, end of sentence, put ' ' as a default value
                        tChar[idxH]=' ';
                    } else if(b[idxC]==',') { // Simply no character, put ' ' as a default value
                        tChar[idxH]=' ';
                    }else{
                        tChar[idxH]=(char) b[idxC];
                        idxC++;
                    }
                    idxH++;
                    break;
            }
            idxParse++;
            idxC++;
        }
        return;
    }
}
